//
//  Eval.c
//  Wood
//
//  Created by Wayne on 9/10/14.
//  Copyright (c) 2014 Wayne. All rights reserved.
//

#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "Arch.h"
#include "Functions.h"

static Value evalBoolExpression(BOOL boolValue) {
    Value v;
    v.type = WdBoolValue;
    v.u.boolValue = boolValue;
    return v;
}

static Value evalIntExpression(int intValue) {
    Value v;
    v.type = WdIntValue;
    v.u.intValue = intValue;
    return v;
}

static Value evalDoubleExpression(double doubleValue) {
    Value v;
    v.type = WdDoubleValue;
    v.u.doubleValue = doubleValue;
    return v;
}

static Value evalStringExpression(String *stringValue) {
    Value v;
    v.type = WdStringValue;
    v.u.stringValue = malloc(sizeof(String));
    v.u.stringValue->string = malloc(sizeof(char) * (strlen(stringValue->string) + 1));
    strcpy(v.u.stringValue->string, stringValue->string);
    return v;
}

static Value evalIdentifierExpression(Frame *frame, Expression *expr) {
    Value v;
    Variable *variable;
    variable = searchVariableFromFrame(frame, expr->u.identifier);
    if (variable != NULL) {
        v = variable->value;
    } else {
        /* TODO: runtime error - variable not found */
    }
    return v;
}

Value evalExpression(Frame *frame, Expression *expr);

static Value evalAssignExpression(Frame *frame, char *name, Expression *expr) {
    Value v;
    Variable *variable;
    v = evalExpression(frame, expr);
    variable = searchVariableFromFrame(frame, name);
    if (variable != NULL) {
        variable->value = v;
    } else {
        addVariableToFrame(frame, name, &v);
    }
    return v;
}

static Value evalBinaryBool(ExpressionType operator, BOOL left, BOOL right) {
    Value v;
    v.type = WdBoolValue;
    if (operator == WdEqExpression) {
        v.u.boolValue = (left == right);
    } else if (operator == WdNeExpression) {
        v.u.boolValue = (left != right);
    } else {
        /* TODO: runtime error - not bool operator */
    }
    return v;
}

static Value evalBinaryInt(ExpressionType operator, int left, int right) {
    Value v;
    if (isMathOperator(operator)) {
        v.type = WdIntValue;
    } else if (isComparisonOperator(operator)) {
        v.type = WdBoolValue;
    } else {
        /* TODO: runtime error - not int operator */
    }
    switch (operator) {
        case WdAddExpression:
            v.u.intValue = left + right;
            break;
        case WdSubExpression:
            v.u.intValue = left - right;
            break;
        case WdMulExpression:
            v.u.intValue = left * right;
            break;
        case WdDivExpression:
            v.u.intValue = left / right;
            break;
        case WdModExpression:
            v.u.intValue = left % right;
            break;
        case WdEqExpression:
            v.u.boolValue = (left == right);
            break;
        case WdNeExpression:
            v.u.boolValue = (left != right);
            break;
        case WdGtExpression:
            v.u.boolValue = (left > right);
            break;
        case WdGeExpression:
            v.u.boolValue = (left >= right);
            break;
        case WdLtExpression:
            v.u.boolValue = (left < right);
            break;
        case WdLeExpression:
            v.u.boolValue = (left <= right);
            break;
        default:
            /* TODO: runtime error - not int operator */
            ;
    }
    return v;
}

static Value evalBinaryDouble(ExpressionType operator, double left, double right) {
    Value v;
    if (isMathOperator(operator)) {
        v.type = WdDoubleValue;
    } else if (isComparisonOperator(operator)) {
        v.type = WdBoolValue;
    } else {
        /* TODO: runtime error - not int operator */
    }
    switch (operator) {
        case WdAddExpression:
            v.u.doubleValue = left + right;
            break;
        case WdSubExpression:
            v.u.doubleValue = left - right;
            break;
        case WdMulExpression:
            v.u.doubleValue = left * right;
            break;
        case WdDivExpression:
            v.u.doubleValue = left / right;
            break;
        case WdModExpression:
            v.u.doubleValue = fmod(left, right);
            break;
        case WdEqExpression:
            v.u.boolValue = (left == right);
            break;
        case WdNeExpression:
            v.u.boolValue = (left != right);
            break;
        case WdGtExpression:
            v.u.boolValue = (left > right);
            break;
        case WdGeExpression:
            v.u.boolValue = (left >= right);
            break;
        case WdLtExpression:
            v.u.boolValue = (left < right);
            break;
        case WdLeExpression:
            v.u.boolValue = (left <= right);
            break;
        default:
            /* TODO: runtime error - not int operator */
            ;
    }
    return v;
}

static Value evalComparisonString(ExpressionType operator, String *left, String *right) {
    Value v;
    v.type = WdBoolValue;
    int cmp;
    cmp = strcmp(left->string, right->string);
    switch (operator) {
        case WdEqExpression:
            v.u.boolValue = (cmp == 0);
            break;
        case WdNeExpression:
            v.u.boolValue = (cmp != 0);
            break;
        case WdGtExpression:
            v.u.boolValue = (cmp > 0);
            break;
        case WdGeExpression:
            v.u.boolValue = (cmp >= 0);
            break;
        case WdLtExpression:
            v.u.boolValue = (cmp < 0);
            break;
        case WdLeExpression:
            v.u.boolValue = (cmp <= 0);
            break;
        default:
            /* TODO: runtime error - not string operator */
            ;
    }
    return v;
}

static String *chainString(String *left, String *right) {
    unsigned long len;
    char *str;
    String *string;
    len = strlen(left->string) + strlen(right->string);
    str = malloc(sizeof(char) * (len + 1));
    strcpy(str, left->string);
    strcat(str, right->string);
    string = createString(str);
    return string;
}

Value evalBinaryExpression(Frame *frame, ExpressionType operator, Expression *left, Expression *right) {
    Value v;
    Value leftValue;
    Value rightValue;
    leftValue = evalExpression(frame, left);
    rightValue = evalExpression(frame, right);
    if (leftValue.type == WdBoolValue && rightValue.type == WdBoolValue) {
        v = evalBinaryBool(operator, leftValue.u.boolValue, rightValue.u.boolValue);
    } else if (leftValue.type == WdIntValue && rightValue.type == WdIntValue) {
        v = evalBinaryInt(operator, leftValue.u.intValue, rightValue.u.intValue);
    } else if (leftValue.type == WdIntValue && rightValue.type == WdDoubleValue) {
        v = evalBinaryDouble(operator, leftValue.u.intValue, rightValue.u.doubleValue);
    } else if (leftValue.type == WdDoubleValue && rightValue.type == WdIntValue) {
        v = evalBinaryDouble(operator, leftValue.u.doubleValue, rightValue.u.intValue);
    } else if (leftValue.type == WdStringValue && operator == WdAddExpression) {
        char buf[1024];
        String *rightString;
        if (rightValue.type == WdBoolValue) {
            if (rightValue.u.boolValue) {
                rightString = createString("true");
            } else {
                rightString = createString("false");
            }
        } else if (rightValue.type == WdIntValue) {
            sprintf(buf, "%d", rightValue.u.intValue);
            rightString = createString(buf);
        } else if (rightValue.type == WdDoubleValue) {
            sprintf(buf, "%f", rightValue.u.doubleValue);
            rightString = createString(buf);
        } else if (rightValue.type == WdStringValue) {
            rightString = rightValue.u.stringValue;
        } else {
            /* TODO: runtime error - bad operand type */
        }
        v.type = WdStringValue;
        v.u.stringValue = chainString(leftValue.u.stringValue, rightString);
    } else if (leftValue.type == WdStringValue && rightValue.type == WdStringValue) {
        v = evalComparisonString(operator, leftValue.u.stringValue, rightValue.u.stringValue);
    } else {
        /* TODO: runtime error - bad operator type */
    }
    return v;
}

static Value evalLogicalExpression(Frame *frame, ExpressionType operator, Expression *left, Expression *right) {
    Value v;
    Value leftValue;
    Value rightValue;
    v.type = WdBoolValue;
    leftValue = evalExpression(frame, left);
    if (leftValue.type != WdBoolValue) {
        /* TODO: runtime error - not bool type */
    }
    if (operator == WdLogicalAndExpression) {
        if (!leftValue.u.boolValue) {
            v.u.boolValue = NO;
            return v;
        }
    } else if (operator == WdLogicalOrExpression) {
        if (leftValue.u.boolValue) {
            v.u.boolValue = YES;
            return v;
        }
    } else {
            /* TODO: runtime error - not bool operator */
    }
    rightValue = evalExpression(frame, right);
    if (rightValue.type != WdBoolValue) {
        /* TODO: runtime error - not bool type */
    }
    v.u.boolValue = rightValue.u.boolValue;
    return v;
}

Value evalMinusExpression(Frame *frame, Expression *operand) {
    Value operandValue;
    Value v;
    operandValue = evalExpression(frame, operand);
    if (operandValue.type == WdIntValue) {
        v.type = WdIntValue;
        v.u.intValue = -operandValue.u.intValue;
    } else if (operandValue.type == WdDoubleValue) {
        v.type = WdDoubleValue;
        v.u.doubleValue = -operandValue.u.doubleValue;
    } else {
        /* TODO: runtime error - not minus operand type */
    }
    return v;
}

static Value callNativeFunction(Frame *frame, Expression *expr, nativeFunctionProc *proc) {
    Value v;
    int argc;
    ArgumentList *argument;
    Value *args;
    int i;
    for (argc = 0, argument = expr->u.functionCallExpression->argument; argument != NULL; argument = argument->next) {
        argc += 1;
    }
    args = malloc(sizeof(Value) * argc);
    for (i = 0, argument = expr->u.functionCallExpression->argument; argument != NULL; argument = argument->next) {
        args[i] = evalExpression(frame, argument->expression);
    }
    v = proc(argc, args);
    free(args);
    return v;
}

static Value callWoodFunction(Frame *frame, Expression *expr, FunctionDefinition *func) {
    Value v;
    ArgumentList *argument;
    ParameterList *parameter;
    Frame *localFrame;
    StatementResult result;
    localFrame = createFrame();
    localFrame->parentFrame = frame;
    for (argument = expr->u.functionCallExpression->argument, parameter = func->u.woodFunction.parameter; argument != NULL; argument = argument->next, parameter = parameter->next) {
        Value argValue;
        if (parameter == NULL) {
            /* TODO: runtime error - argument to many */
        }
        argValue = evalExpression(frame, argument->expression);
        addVariableToFrame(localFrame, parameter->name, &argValue);
    }
    if (parameter != NULL) {
        /* TODO: runtime error - argument to few */
    }
    result = executeStatementList(localFrame, func->u.woodFunction.block->statementList);
    if (result.type == WdReturnStatement) {
        v = result.u.returnValue;
    } else {
        v.type = WdNullValue;
    }
    disposeFrame(localFrame);
    return v;
}

static Value evalFunctionCallExpression(Frame *frame, Expression *expr) {
    Value v;
    FunctionDefinition *function;
    char *identifier = expr->u.functionCallExpression->identifier;
    function = searchFunctionDefinitionFromFrame(frame, identifier);
    if (function == NULL) {
        /* TODO: runtime error - function not found */
    }
    if (function->type == WdNativeFunctionDefinition) {
        v = callNativeFunction(frame, expr, function->u.nativeFunction.proc);
    } else {
        v = callWoodFunction(frame, expr, function);
    }
    return v;
}

Value evalExpression(Frame *frame, Expression *expr) {
    Value v;
    switch (expr->type) {
        case WdBoolExpression:
            v = evalBoolExpression(expr->u.boolValue);
            break;
        case WdIntExpression:
            v = evalIntExpression(expr->u.intValue);
            break;
        case WdDoubleExpression:
            v = evalDoubleExpression(expr->u.doubleValue);
            break;
        case WdStringExpression:
            v = evalStringExpression(expr->u.stringValue);
            break;
        case WdIdentifierExpression:
            v = evalIdentifierExpression(frame, expr);
            break;
        case WdAssignExpression:
            v = evalAssignExpression(frame, expr->u.assignExpression->variable, expr->u.assignExpression->operand);
            break;
        case WdAddExpression:
        case WdSubExpression:
        case WdMulExpression:
        case WdDivExpression:
        case WdModExpression:
        case WdEqExpression:
        case WdNeExpression:
        case WdGtExpression:
        case WdGeExpression:
        case WdLtExpression:
        case WdLeExpression:
            v = evalBinaryExpression(frame, expr->type, expr->u.binaryExpression->left, expr->u.binaryExpression->right);
            break;
        case WdLogicalAndExpression:
        case WdLogicalOrExpression:
            v = evalLogicalExpression(frame, expr->type, expr->u.binaryExpression->left, expr->u.binaryExpression->right);
            break;
        case WdMinusExpression:
            v = evalMinusExpression(frame, expr->u.minusExpression);
            break;
        case WdFunctionCallExpression:
            v = evalFunctionCallExpression(frame, expr);
            break;
        default:
            /* TODO: runtime error - bad operator */
            ;
    }
    return v;
}